﻿using SharpDX;
using SharpDX.D3DCompiler;
using SharpDX.Direct3D;
using SharpDX.Direct3D11;
using SharpDX.DXGI;
using SharpDX.WPF;
using Buffer = SharpDX.Direct3D11.Buffer;
using System.Runtime.InteropServices;
using System;

namespace Week01D3D11Tutorials
{
	// DirectX SDK: Tutorial 4: 3D Spaces
	// http://msdn.microsoft.com/en-us/library/ff729721(v=VS.85).aspx
	public class T4_Spaces3D : D3D11
	{
		[StructLayout(LayoutKind.Sequential, Pack = 4)]
		public struct VectorColor
		{
			public VectorColor(Vector3 p, Color4 c)
			{
				Point = p;
				Color = c;
			}
			public Vector3 Point;
			public Color4 Color;

			public const int SizeInBytes = (3 + 4) * 4;
		}

		[StructLayout(LayoutKind.Sequential, Pack = 4)]
		public struct Projections
		{
			public Matrix World;
			public Matrix View;
			public Matrix Projection;
		}

		protected override void Dispose(bool disposing)
		{
			base.Dispose(disposing);
			// NOTE: SharpDX 1.3 requires explicit Dispose() of everything
			Set(ref g_pVertexShader, null);
			Set(ref g_pPixelShader, null);
			Set(ref g_pConstantBuffer, null);
		}

		VertexShader g_pVertexShader;
		PixelShader g_pPixelShader;
		ConstantBuffer<Projections> g_pConstantBuffer;

		Matrix g_Projection;

		public T4_Spaces3D()
		{
			using (var dg = new DisposeGroup())
			{
				// --- init shaders
				ShaderFlags sFlags = ShaderFlags.EnableStrictness;
#if DEBUG
				sFlags |= ShaderFlags.Debug;
#endif
				var pVSBlob = dg.Add(ShaderBytecode.CompileFromFile("T4_Spaces3D.fx", "VS", "vs_4_0", sFlags, EffectFlags.None));
				var inputSignature = dg.Add(ShaderSignature.GetInputSignature(pVSBlob));
				g_pVertexShader = new VertexShader(Device, pVSBlob);

				var pPSBlob = dg.Add(ShaderBytecode.CompileFromFile("T4_Spaces3D.fx", "PS", "ps_4_0", sFlags, EffectFlags.None));
				g_pPixelShader = new PixelShader(Device, pPSBlob);

				// --- let DX know about the pixels memory layout
				var layout = new InputLayout(Device, inputSignature, new[]{
					new InputElement("POSITION", 0, Format.R32G32B32_Float, 0),
					new InputElement("COLOR", 0, Format.R32G32B32A32_Float, 12, 0),
				});
				Device.ImmediateContext.InputAssembler.SetInputLayout(layout);
				dg.Add(layout);

				// --- init vertices
				var vertexBuffer = DXUtils.CreateBuffer(Device, new[]{
					new VectorColor(new Vector3(-1.0f,  1.0f, -1.0f), new Color4(1.0f, 0.0f, 0.0f, 1.0f)),
					new VectorColor(new Vector3( 1.0f,  1.0f, -1.0f), new Color4(1.0f, 0.0f, 1.0f, 0.0f)),
					new VectorColor(new Vector3( 1.0f,  1.0f,  1.0f), new Color4(1.0f, 0.0f, 1.0f, 1.0f)),
					new VectorColor(new Vector3(-1.0f,  1.0f,  1.0f), new Color4(1.0f, 1.0f, 0.0f, 0.0f)),
					new VectorColor(new Vector3(-1.0f, -1.0f, -1.0f), new Color4(1.0f, 1.0f, 0.0f, 1.0f)),
					new VectorColor(new Vector3( 1.0f, -1.0f, -1.0f), new Color4(1.0f, 1.0f, 1.0f, 0.0f)),
					new VectorColor(new Vector3( 1.0f, -1.0f,  1.0f), new Color4(1.0f, 1.0f, 1.0f, 1.0f)),
					new VectorColor(new Vector3(-1.0f, -1.0f,  1.0f), new Color4(1.0f, 0.0f, 0.0f, 0.0f)),
				});
				dg.Add(vertexBuffer);
				Device.ImmediateContext.InputAssembler.SetVertexBuffers(0, new VertexBufferBinding(vertexBuffer, VectorColor.SizeInBytes, 0));

				// --- init indices
				var indicesBuffer = DXUtils.CreateBuffer(Device, new ushort[] {
					3,1,0,
					2,1,3,

					0,5,4,
					1,5,0,

					3,4,7,
					0,4,3,

					1,6,5,
					2,6,1,

					2,7,6,
					3,7,2,

					6,4,5,
					7,4,6,
				});
				dg.Add(indicesBuffer);
				Device.ImmediateContext.InputAssembler.SetIndexBuffer(indicesBuffer, Format.R16_UInt, 0);

				Device.ImmediateContext.InputAssembler.SetPrimitiveTopology(PrimitiveTopology.TriangleList);

				// --- create the constant buffer
				g_pConstantBuffer = new ConstantBuffer<Projections>(Device);
				Device.ImmediateContext.VertexShader.SetConstantBuffer(0, g_pConstantBuffer.Buffer);
			}

			CurrentCamera = new FirstPersonCamera();
			CurrentCamera.SetProjParams((float)Math.PI / 2, 1, 0.01f, 100.0f);
			CurrentCamera.SetViewParams(new Vector3(0.0f, 0.0f, -5.0f), new Vector3(0.0f, 1.0f, 0.0f));
		}

		public override void Reset(int w, int h)
		{
			base.Reset(w, h);
			g_Projection = Matrix.PerspectiveFovLH((float)Math.PI / 2, w / (float)h, 0.01f, 100.0f);
		}

		public override void RenderScene(DrawEventArgs args)
		{
			//
			// Animate the cube
			//
			float t = (float)args.TotalTime.TotalSeconds;
			var g_World = Matrix.RotationY( t );

			//
			// Clear the back buffer
			//
			Device.ImmediateContext.ClearRenderTargetView(this.RenderTargetView, new Color4(1.0f, 0.3f, 0.525f, 0.8f));

			//
			// Update variables
			//
			g_pConstantBuffer.Value = new Projections
			{
				World = Matrix.Transpose(g_World),
				View = Matrix.Transpose(CurrentCamera.View),
				Projection = Matrix.Transpose(g_Projection),
			};

			//
			// Renders a triangle
			//
			Device.ImmediateContext.VertexShader.Set(g_pVertexShader);
			Device.ImmediateContext.VertexShader.SetConstantBuffer(0, g_pConstantBuffer.Buffer);
			Device.ImmediateContext.PixelShader.Set(g_pPixelShader);
			Device.ImmediateContext.DrawIndexed( 36, 0, 0 );        // 36 vertices needed for 12 triangles in a triangle list
		}
	}
}
